#ifndef DIAMOND_SQUARE_ALGORITHM_H
#define DIAMOND_SQUARE_ALGORITHM_H

#include "../WrappedContainers/DynamicArray2.h"
#include <math.h>
#include <limits>

/******************************************************************************************
 * Modified version of a C# diamond-square algorithm (plasma fractal).
 * Original source: http://www.smokycogs.com/blog/plasma-fractals/ (19.05.2011).
 ******************************************************************************************/

class DiamondSquareAlgorithm {
protected:

	/**
	 * Returns a random float value in range 0..1
	 */
	static inline float randomValue() {
		return (rand() % std::numeric_limits<short>::max())/float(std::numeric_limits<short>::max());
	}

	static inline float rectify(float num) {
		if (num < 0) {
			num = 0;
		} else if (num > 1.0) {
			num = 1.0;
		}
		return num;
	}

	static inline float displace(float smallSize, float bigSize, float roughness) {
		const float max = smallSize / bigSize * roughness;
		return (randomValue() - 0.5) * max;
	}

	static void DivideGrid(DynamicArray2<float>& points, float x, float y, float width, float height, float c1, float c2, float c3, float c4, float bigSize, float roughness) {
		float edge1, edge2, edge3, edge4, middle;

		const float newWidth = floor(width / 2);
		const float newHeight = floor(height / 2);

		if (width > 1 || height > 1) {
			middle = ((c1 + c2 + c3 + c4) / 4)+displace(newWidth + newHeight, bigSize, roughness);	//Randomly displace the midpoint!

			//Calculate the edges by averaging the two corners of each edge.
			edge1 = ((c1 + c2) / 2); 
			edge2 = ((c2 + c3) / 2);
			edge3 = ((c3 + c4) / 2);
			edge4 = ((c4 + c1) / 2);

			//Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!
			middle= rectify(middle);
			edge1 = rectify(edge1);
			edge2 = rectify(edge2);
			edge3 = rectify(edge3);
			edge4 = rectify(edge4);

			//Do the operation over again for each of the four new grids.
			DivideGrid(points, x, y, newWidth, newHeight, c1, edge1, middle, edge4, bigSize, roughness);
			DivideGrid(points, x + newWidth, y, width - newWidth, newHeight, edge1, c2, edge2, middle, bigSize, roughness);
			DivideGrid(points, x + newWidth, y + newHeight, width - newWidth, height - newHeight, middle, edge2, c3, edge3, bigSize, roughness);
			DivideGrid(points, x, y + newHeight, newWidth, height - newHeight, edge4, middle, edge3, c4, bigSize, roughness);
		} else {
			const float c = (c1 + c2 + c3 + c4) / 4;

			points((int)(x), (int)(y)) = c;
			if (width == 2) {
				points((int)(x+1), (int)(y)) = c;
			}
			if (height == 2) {
				points((int)(x), (int)(y+1)) = c;
			}
			if ((width == 2) && (height == 2)) {
				points((int)(x + 1), (int)(y+1)) = c;
			}
		}
	}

public:
	static inline void Generate(DynamicArray2<float>& points, float roughness, float cornerHeight) {
		const float bigSize = points.Size1() + points.Size2(); 
		DivideGrid(points, 0, 0, points.Size1(), points.Size2(), cornerHeight, cornerHeight, cornerHeight, cornerHeight, bigSize, roughness);
	}
};

#endif //DIAMOND_SQUARE_ALGORITHM_H
